package net.helix.engine;

import java.awt.Dimension;
import java.awt.Frame;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ProgressMonitor;
import javax.swing.SwingUtilities;

import net.helix.core.geometry.Geometry_Image;
import net.helix.core.loader.Model3DSLoader;
import net.helix.core.loader.OBJLoader;
import net.helix.engine.library.Library;
import net.helix.engine.library.items.LibraryItem;
import net.helix.engine.library.items.LibraryItem_Image;
import net.helix.engine.properties.Properties_Stage;
import net.helix.engine.scene.Scene;
import net.helix.engine.timeline.Timeline;
import net.helix.engine.timeline.layer.TimelineLayer;
import net.helix.engine.timeline.layer.TimelineLayerType;
import net.helix.ui.UIEngine;
import net.helix.ui.UIEngineTop;
import net.helix.ui.panels.PopupLoader;
import net.helix.ui.panels.SharedGLCanvas;
import net.helix.ui.panels.library.LibraryTreeTableModel;
import net.helix.ui.panels.view.ViewPreview;

public class Engine {
	public static int sceneCounter = 0;
	public static int libraryCounter = 0;
	
	public ArrayList<Scene> scenes;
	public ArrayList<Library> libraries;
	
	private Properties_Stage projectProperties = null;
	
	
	private ViewPreview currentSelectedView = null;
	// Views contain viewPaths, not the Engine.
	//private ViewPath currentViewPath = null;
	private Scene currentSelectedScene = null;
	private Timeline currentSelectedTimeline = null;
	
	private Library currentSelectedLibrary = null;
	
	// Selected elements exist now exclusively in each View
	//private ArrayList<Element> currentSelectedElements = null;
	
	public ArrayList<TimelineLayer> possibleViews = null;
	
	public Frame mainWindow;
	
	public SharedGLCanvas sharedGL = new SharedGLCanvas();
	
	private UIEngine ui;
	
	
	/**
	 *  A boolean to determine if changing around anything should update any displays.
	 *  Will be set to false for things like loading a project from a template or file
	 *  to drastically reduce the time it takes to populate all of the data.
	 */
	private boolean updateGraphics = false;
	
	
	/**
	 * Create the main engine that will be used to control almost every technical detail
	 * about a project. Set <tt>setUIEngine()</tt> should be called not too long after the
	 * creation of the engine, which is called in the constructor of the UIEngine class.
	 * @param mainWindow - The window that the engine will be created on.
	 */
	public Engine(Frame mainWindow){
		this.mainWindow = mainWindow;
		
		scenes = new ArrayList<Scene>();
		
		libraries = new ArrayList<Library>();
		
		
	}
	/**
	 * Make sure this is called as soon as possible. Just in case the engine needs to tell the ui to update something.
	 * @param ui
	 */
	public void setUIEngine(UIEngine ui){
		this.ui = ui;
	}
	/**
	 * Get the UIEngine associated with this engine.
	 * @return
	 */
	public UIEngine getUIEngine(){
		return ui;
	}
	
	/**
	 * Allows changes like adding a new layer, frame, or new element to a
	 * frame to call their associated ui panels to redraw.
	 */
	public void enableUpdateGraphics(){
		updateGraphics = true;
	}
	/**
	 * Disables redrawing during changes of the engine so that it can be achieved
	 * in less time.
	 */
	public void disableUpdateGraphics(){
		updateGraphics = false;
	}
	/**
	 * Should call all open panels to update their displays... Will update graphics
	 * regardless of <tt>disableUpdateGraphics()</tt> being set.
	 */
	public void updateAllGraphics(){
		if(ui != null){
			if(ui.getCurrentTimelinePane() != null){
				ui.getCurrentTimelinePane().reloadLayersList();
				ui.getCurrentTimelinePane().UpdateTimeline();
			}
		}
		
	}
	
	/**
	 * Creates a new scene, gives it a default name that should probably
	 * be renamed later.
	 */
	public void addNewScene(){
		scenes.add(new Scene("Scene "+(++sceneCounter), true));
	}
	/**
	 * Creates a new library, gives it a default name that should probably
	 * be renamed later.
	 */
	public void addNewLibrary(){
		libraries.add(new Library(this, "Library "+(++sceneCounter)));
	}
	/**
	 * Creates the default template when you create a new, empty project.
	 * Creates a new scene, and fills it with a 3d camera and 2d camera
	 * both with 1 layer.
	 * @return - A reference to the <tt>Engine</tt> for convenience to pass
	 * into the constructor of the <tt>UIEngine</tt>.
	 */
	public Engine loadInitialTemplate(){
		// TODO: set all of the status, like framerate and background color and
		disableUpdateGraphics();
		
		projectProperties = new Properties_Stage(true);
		
		addNewScene();
		addNewLibrary();
		
		//currentViewPath = new ViewPath();
		//currentViewPath.setRoot(new ViewNode(scenes.get(0), 0));
		
		setCurrentSelectedScene(scenes.get(0));
		
		//setCurrentSelectedTimeline(currentViewPath.getTimelineAtEnd());
		//setCurrentSelectedLayer(null);
		//setFrame(currentViewPath.getFrameAtEnd()); // frames in Helix WILL be 0 based.
		setCurrentSelectedTimeline(getCurrentSelectedScene().getTimeline());
		
		
		
		setCurrentSelectedLibrary(libraries.get(0));
		
		
		enableUpdateGraphics();
		updateAllGraphics();
		
		return this;
	}
	/**
	 * Set the view that is currently selected. Also sets the current timeline to
	 * match the one in the view.
	 * @param view
	 */
	public void setCurrentSelectedView(ViewPreview view){
		this.currentSelectedView = view;
		
		
		setCurrentSelectedTimeline(currentSelectedView.getTimeline());
		
		if(updateGraphics){
			ui.getCurrentTimelinePane().reloadLayersList();
			ui.getCurrentTimelinePane().UpdateTimeline();
		}
	}
	public ViewPreview getCurrentSelectedView(){
		return currentSelectedView;
	}
	/**
	 * Sets the current scene. When an animation takes more than one scene, use this to traverse between them.
	 * @param scene
	 */
	public void setCurrentSelectedScene(Scene scene){
		// TODO: Assert scene exists
		currentSelectedScene = scene;
		// make it update all views and the timeline and such...
	}
	/**
	 * Sets the current timeline. This is the timeline that displays inside the timeline pane. When you are
	 * editing the content of a movieclip for instance, the currently selected timeline will be set to the
	 * clip's timeline. If you are editing an object without a timeline set it to null.
	 * @param timeline
	 */
	public void setCurrentSelectedTimeline(Timeline timeline){
		// TODO: Assert timeline exists in scene
		currentSelectedTimeline = timeline;
		
		if(updateGraphics){
			if(ui.getCurrentTimelinePane() != null){
				ui.getCurrentTimelinePane().UpdateTimeline();
			}
		}
	}
	/**
	 * Set the current library. More than 1 library can be in a project for organizational purposes and to make
	 * importing into other projects easy. Perhaps there can be distributed read-only libraries that can be
	 * used to aid animations. Maybe a market can be created around such a thing.
	 * @param library
	 */
	public void setCurrentSelectedLibrary(Library library){
		// TODO: Assert that the layer exists inside the scene and currentSelectedTimeline
		currentSelectedLibrary = library;
	}
	/**
	 * Gets the current project properties (perhaps more than 1 can be
	 * saved so that it's easier for the user to switch between properties).
	 * @return
	 */
	public Properties_Stage getProjectProperties(){
		return projectProperties;
	}
	/**
	 * Get the current scene.
	 * @return
	 */
	public Scene getCurrentSelectedScene(){
		return currentSelectedScene;
	}
	/**
	 * Get the current timeline. Can be null if the user is editing an object with no timeline.
	 * @return
	 */
	public Timeline getCurrentSelectedTimeline(){
		return currentSelectedTimeline;
	}
	
	/**
	 * Get the current selected library.
	 * @return
	 */
	public Library getCurrentSelectedLibrary(){
		return currentSelectedLibrary;
	}
	/**
	 * Iterate through all layers on the current scene (or timeline if you are in a clip) to
	 * determine what possible Views there should be.
	 * @return
	 */
	public ArrayList<TimelineLayer> getListOfPossibleViews(){
		if(possibleViews == null){
			possibleViews = new ArrayList<TimelineLayer>();
			// calculate possible views
			if(getCurrentSelectedScene() != null){
				Timeline timeline = getCurrentSelectedScene().getTimeline();
				
				int l = timeline.layers.size();
				for(int i=0;i<l;i++){
					doListOfPossibleViews(timeline.layers.get(i));
				}
			}
		}
		return possibleViews;
	}
	/**
	 * Helper function to getListOfPossibleViews() offering a recursive solution
	 * in case the camera layers are contained in folders.
	 * @param layer
	 */
	private void doListOfPossibleViews(TimelineLayer layer){
		if(layer.type == TimelineLayerType.CAMERA){
			possibleViews.add(layer);
		}
		if(layer.type == TimelineLayerType.FOLDER){
			int l = layer.children.size();
			for(int i=0;i<l;i++){
				doListOfPossibleViews(layer.children.get(i));
			}
		}
	}
	/**
	 * Import images from the file system. Takes model as a parameter which
	 * is the timeline graphics that will be updated after the import.
	 * @param files - An array of files which contain the addresses of
	 * the files to import.
	 * @param model - <tt>LibraryTreeModel</tt> of the library that the new
	 * item will be added to.
	 */
	// TODO: remove the LibraryTreeTableModel part of the parameter and make the engine look it up itself
	public void handleImageImport(File[] files, LibraryTreeTableModel model){
		for(int i=0;i<files.length;i++){
			//currentSelectedLibrary.addNewItem(new LibraryItem_Image(files[i].getName()));
			
			LibraryItem_Image newImage = new LibraryItem_Image(model.getLibrary(), files[i].getName(), files[i]);
			// TODO: force the image to attempt loading (still not binding to openGL yet..)
			// if it can't load, then skip this specific image and report an error.
			
			// Add it to the library
			// TODO: perhaps put it in the last selected folder...
			model.addNewItem(newImage);
		}
	}
	/**
	 * Import sounds from the file system. Takes model as a parameter which
	 * is the timeline graphics that will be updated after the import.
	 * @param files - An array of files which contain the addresses of
	 * the files to import.
	 * @param model - <tt>LibraryTreeModel</tt> of the library that the new
	 * item will be added to.
	 */
	public void handleSoundImport(File[] files, LibraryTreeTableModel model){
		
	}
	
	public static JTextArea loadingFieldText = new JTextArea(20, 50);
	private static JFrame loadingFrame;
	
	private static JScrollPane scrollPane;
	
	static{
		loadingFrame = new JFrame();
		
		//outputText.setMinimumSize(new Dimension(300,200));
		
		scrollPane = new JScrollPane(loadingFieldText);
		
		loadingFrame.add(scrollPane);
		
		loadingFrame.pack();
		
		
	}
	public static void addLoadingText(String text){
		String t = loadingFieldText.getText();
		
		int lines = 0;
		int i = 0;
		while(true){
			i = t.indexOf('\n', i+1);
			if(i == -1){
				break;
			}else{
				lines++;
			}
		}
		System.out.println("lines "+lines);
		while(lines >= loadingFieldText.getRows()){
			t = t.substring(t.indexOf('\n')+1, t.length()-1);
			lines--;
		}
		
		loadingFieldText.setText(t + text+".\n\r");
		loadingFieldText.setCaretPosition(loadingFieldText.getText().length()-1);
	}
	
	
	public void handleModelImport(final File[] files, final LibraryTreeTableModel model){
		
		System.out.println("Begin importing "+files.length + ((files.length==1)?" file":" files"));
		
		
		final Engine e = this;
		
		/*
		SwingUtilities.invokeLater(new Runnable(){
			public void run(){
				for(int i=0;i<files.length;i++){
					String name = files[i].getName();
					int namesplit = name.lastIndexOf('.');
					if(namesplit > 0){
						String end = name.substring(namesplit + 1);
						
						if(end.equalsIgnoreCase("obj")){
							new OBJLoader(e, model, files[i].getPath());
						}
						if(end.equalsIgnoreCase("3ds")){
							new Model3DSLoader(e, model, files[i].getPath());
						}				
					}
				}
			}
		});
		*/
		
		
		new Thread(new Runnable(){
			public void run(){
				for(int i=0;i<files.length;i++){
					addLoadingText("Adding "+files[i].getName());
					
					String name = files[i].getName();
					int namesplit = name.lastIndexOf('.');
					if(namesplit > 0){
						String end = name.substring(namesplit + 1);
						
						if(end.equalsIgnoreCase("obj")){
							new OBJLoader(e, model, files[i].getPath());
						}
						if(end.equalsIgnoreCase("3ds")){
							new Model3DSLoader(e, model, files[i].getPath());
						}				
					}
				}
				loadingFrame.setVisible(false);
			}
		}
		).start();
		
		
		loadingFrame.setVisible(true);
		
	}
	
	
	
	public HashMap<String, LoadedImage> imageFilesAdded = new HashMap<String, LoadedImage>();
	class LoadedImage{
		Geometry_Image image = null;
		LibraryItem_Image libImage = null;
	}
	// TODO: add a parameter that determines the location it adds it to
	public Geometry_Image loadImageAndAddToLibrary(LibraryTreeTableModel model, String addr){
		File file = new File(addr);
		
		if(file.exists() == false){
			// does not exist...
			return null;
		}
		
		
		
		/*
		Texture tex = null;
		try {
			tex = TextureLoader.getTexture(predictfiletype(file.getName()), ResourceLoader.getResourceAsStream(file.getPath()));
		} catch (IOException e) {
			e.printStackTrace();
		}
		*/
		
		// check if it already exists
		LoadedImage cli = imageFilesAdded.get(file.getAbsolutePath());
		
		if(cli != null){
			LoadedImage li = cli;
			
			if(li.libImage == null && model != null){
				li.libImage = new LibraryItem_Image(model.getLibrary(), file.getName(), li.image);
				
				
				addLoadingText("Adding "+li.libImage.name + ".");
				
				// add to library
				model.addNewItem(li.libImage);
			}
			return li.image;
		}else{
			LoadedImage li = new LoadedImage();
			
			li.image = new Geometry_Image(addr);
			
			if(model != null){
				li.libImage = new LibraryItem_Image(model.getLibrary(), file.getName(), li.image);
				
				// add to library
				model.addNewItem(li.libImage);
			}
			
			// mark file as loaded
			imageFilesAdded.put(file.getAbsolutePath(), li);
			
			return li.image;
		}
		
		
	}
	
	
	public static String predictfiletype(String name){
		int namesplit = name.lastIndexOf('.');
		if(namesplit > 0){
			String end = name.substring(namesplit + 1);
			
			if(end.equalsIgnoreCase("png")){
				return "PNG";
			}
			if(end.equalsIgnoreCase("gif")){
				return "GIF";
			}
			if(end.equalsIgnoreCase("bmp")){
				return "BMP";
			}
			if(end.equalsIgnoreCase("jpeg") || end.equalsIgnoreCase("jpg")){
				return "JPG";
			}
		}
		return "PNG";
		
	}
	
}
